OpenCode — 开源 AI 编程 Agent

OpenCode 是由 Anomaly 开发的开源 AI 编程 Agent, 支持终端(TUI)、桌面应用、IDE 扩展三种使用形态。 GitHub 190K+ Stars,月活开发者 750 万+, 支持 75+ LLM 提供商及本地模型,是目前社区最活跃的 AI 编程工具之一。

仓库github.com/anomalyco/opencode
官网opencode.ai
许可证MIT
技术栈TypeScript, Bun, SST (Serverless Stack)
安装npm / brew / pacman / scoop / choco / curl install script

1. 概述

OpenCode 是一个AI 编码代理(Coding Agent),能理解自然语言指令, 自主调用工具(读写文件、执行命令、搜索代码、获取网页等)来完成软件工程任务。 它不只是一个代码补全工具,而是一个能理解项目上下文、制定计划、执行多步操作的智能体。

定位提醒:做业务系统时,组合好流行权威的技术栈(如 Spring Boot + Vue 3 这类生态成熟、人才充足、坑已被踩平的主流组合)比依赖 Agent 更重要。 Agent 只是执行工具,替代不了技术选型的正确性;选错/选小众技术栈,Agent 也救不回来,反而会加速在坑里打转。 先用权威技术栈搭好稳固骨架,再用 Agent 提升开发效率。

核心特性

AI Agent 能力边界

OpenCode 这类工具叫什么

OpenCode 这类工具在业界通常被称为 AI Agent(AI 智能体), 更精确地说是 AI 编程 Agent(AI Coding Agent / 编程代理)。 它与普通 Chatbot 的本质区别在于能自主完成任务—— 读代码、执行命令、改文件、验证结果,而不只是做单轮问答(那种只叫 Chatbot/Assistant)。

叫法说明典型工具
AI Coding Agent / 编程代理 最通用的说法,强调自主完成软件工程任务 OpenCode、Claude Code、Devin
Agentic IDE / Agent 化编辑器 内嵌在 IDE 里的形态 Cursor、Windsurf
CLI Agent / 终端智能体 命令行形态 OpenCode、Claude Code CLI

Harness Engineering(驾驭工程)

Harness(缰绳/驾驭)工程指在构建 AI Agent 时, 把模型自由度较大的输出"驾驭"到一个安全、可控、可审计、可终止的交互框架里的工程实践。 模型本身能力不稳定,不能让它随意执行,因此需要设计一层约束,让 Agent 的行为可预测、可控、可终止。

Harness 层典型内容:

分层视角:OpenCode 本身就是 Harness 的一个具体实现—— 模型层(DeepSeek/Claude 等,只管生成 token)之上,OpenCode 负责提供工具、权限审批、会话循环、 状态管理、代码库索引、UI 集成(即 harness 核心:session loop、tools、permissions、agent 调度)。 换句话说,开发 OpenCode 这类工具 = Harness Engineering;使用 OpenCode = 使用别人写好的 harness。

1.1 CLI 前端技术栈

OpenCode 的终端界面(TUI)基于自研框架 OpenTUI,使用 SolidJS 作为响应式 UI 层,直接渲染到终端。

层级技术说明
TUI 框架 @opentui/core + @opentui/solid + @opentui/keymap Anomaly 自研的终端 UI 框架,将 SolidJS 组件渲染为 ANSI 终端输出,支持键盘映射
UI 框架 SolidJS 细粒度响应式 UI 库,编译后无虚拟 DOM,性能优于 React/Vue
运行时 Bun JavaScript/TypeScript 运行时 + 包管理器 + 打包器,取代 Node.js
语言 TypeScript 全栈 TypeScript,统一前后端类型
终端模拟 @lydell/node-pty 在 TUI 内运行子进程(shell 命令、git 操作等)并捕获输出
Markdown 渲染 marked + shiki 将 LLM 返回的 Markdown 转换并高亮渲染到终端
模糊搜索 fuzzysort @ 文件引用、命令面板等场景的即时模糊匹配
状态管理 effect (Effect-TS) 函数式 Effect 系统,管理异步操作、错误处理、资源生命周期
Diff 算法 @pierre/diffs + diff 代码变更的差异计算与终端内 diff 预览
为何选择 SolidJS 而非 React/Ink?Ink 是社区主流 TUI 方案,但基于 React 的虚拟 DOM 在终端渲染场景存在性能开销。OpenTUI 直接使用 SolidJS 的细粒度响应式更新——只有数据变更的局部 DOM 节点才会重新渲染,在终端大量流式输出的场景下表现更好。

2. 行为分析(做什么)

以下从行为视角分析 OpenCode 的功能边界与运行流程。

2.1 用例图 — 用户与 OpenCode 的交互全景

展示三类主要参与者与 OpenCode 系统的功能交互边界。

OpenCode 用例图
图 1:OpenCode 顶层用例图

2.2 活动图 — 一次典型 Session 的执行流程

展示从用户输入到代码变更的完整活动链,覆盖 Plan 模式与 Build 模式的分支。

OpenCode 会话活动图
图 2:OpenCode Session 执行活动图(带泳道)

2.3 序列图 — System Prompt 组装与 ReAct 循环(以 DeepSeek V4 Pro 为例)

以 DeepSeek V4 Pro 为例(模型 ID 不命中 claude/gpt/gemini/kimi/trinity 等关键词,兜底使用 default.txt)。该 Prompt 中无显式 ReAct 自动继续指令,循环全由代码层驱动。序列图分两阶段:System Prompt 组装 + ReAct 循环的完整退出决策链。

OpenCode System Prompt组装与ReAct循环序列图
图 3:以 DeepSeek V4 Pro 为例 — System Prompt 组装 → ReAct 循环(含 7 个退出判定点)

ReAct 循环退出条件总览(prompt.ts:1088 while(true))

OpenCode 的 ReAct 循环不只依赖 LLM 有无 tool_call 一个条件来判定是否终止,完整的退出决策包含 7 个判定条件,分布在循环体头部、尾部和 Processor 内部:

#位置判定条件行为
1 prompt.ts:1111-1130
(循环体头部)
上一轮 assistant 已标记 finish 且 finish ≠ "tool-calls",且当前 assistant 消息中没有待执行的 tool_call break — 正常结束。若存在孤儿中断工具则记录 warn 日志后退出
2 prompt.ts:1157
(compaction 任务)
上下文压缩处理的返回值为 "stop"(压缩过程出错/无意义) break — 中止会话。正常压缩则 continue
3 prompt.ts:1280 + runner/llm.ts:198
(maxSteps 上限)
step >= agent.steps:达该 Agent 的步数上限 注入 MAX_STEPS_PROMPT 强制 LLM 用纯文本总结;V2 额外设 toolChoice:"none" 协议层禁用工具
4 prompt.ts:1287-1292
(structured output 已捕获)
用户请求了 json_schema 格式输出,LLM 调用 StructuredOutput 工具成功返回 break — 结构化输出已收到,不需要继续循环
5 prompt.ts:1300-1307
(content-filter 过滤)
助手已完成(finish 存在且不是 "tool-calls"/"unknown"),且 finish === "content-filter" break — 提供商内容过滤器拦截,返回错误给用户
6 prompt.ts:1318
+ processor.ts:631
(工具被拒/被阻断)
Processor 返回 "stop":工具的权限请求被用户 deny/reject,且 experimental.continue_loop_on_deny !== true(默认 false) break — 工具被拒后循环终止。若开启 continue_loop_on_deny 实验性开关则 shouldBreak = false,LLM 会看到 error tool_result 并尝试替代方案
7 prompt.ts:1319-1327
(上下文溢出→压缩)
Processor 返回 "compact" — 上下文 token 超过模型窗口上限 创建 compaction 任务 → continue。下一轮循环头部会处理 compaction,压缩后 自动继续(注入 "Continue if you have next steps...")
核心结论:LLM 的 tool_call 只是触发 进入 下一轮循环的条件,而退出循环由另 6 个独立条件决定。即便是纯文本响应(无 tool_call),也需要先过判定 3-7 才能确认退出。尤其是 maxSteps(判定 3)会在 LLM 仍想继续调用工具时强行终止;工具被拒(判定 6)是典型的安全阀门——用户说"不"后循环立刻停止,不会给 LLM 换种方式再试的机会(除非显式开启实验性开关)。

3. 结构分析(由什么组成)

以下从结构视角分析 OpenCode 的组件架构与核心抽象。

3.1 组件图 — 系统宏观架构

展示 OpenCode 的分层组件架构及组件间的依赖关系。

OpenCode 组件架构图
图 4:OpenCode 系统组件架构图

3.2 概念类图 — 核心抽象与关系

展示 OpenCode 中关键概念实体及其关系,不涉及具体实现细节。

OpenCode 概念类图
图 5:OpenCode 核心概念类图

4. 配置示例与 Demo

4.1 基础配置(opencode.json)

项目级配置,设置模型、权限、自动更新等。

{
  "$schema": "https://opencode.ai/config.json",
  "model": "anthropic/claude-sonnet-4-5",
  "small_model": "anthropic/claude-haiku-4-5",
  "autoupdate": true,
  "share": "manual",
  "snapshot": true,
  "instructions": ["CONTRIBUTING.md", "docs/guidelines.md"],
  "permission": {
    "edit": "allow",
    "bash": {
      "*": "ask",
      "git *": "allow",
      "npm *": "allow",
      "rm *": "deny"
    }
  },
  "provider": {
    "anthropic": {
      "options": {
        "timeout": 600000,
        "setCacheKey": true
      }
    }
  },
  "disabled_providers": ["openai"],
  "compaction": {
    "auto": true,
    "prune": false,
    "reserved": 10000
  }
}

4.2 自定义 Agent

JSON 方式:

{
  "agent": {
    "code-reviewer": {
      "description": "Reviews code for best practices and potential issues",
      "mode": "subagent",
      "model": "anthropic/claude-sonnet-4-5",
      "temperature": 0.1,
      "steps": 10,
      "permission": {
        "edit": "deny",
        "bash": "deny"
      }
    }
  }
}

Markdown 方式(.opencode/agents/docs-writer.md):

---
description: Writes and maintains project documentation
mode: subagent
permission:
  bash: deny
---
You are a technical writer. Create clear, comprehensive documentation.
Focus on:
- Clear explanations
- Proper structure
- Code examples
- User-friendly language

4.3 自定义 Skill

在 .opencode/skills/git-release/SKILL.md 中定义可复用技能:

---
name: git-release
description: Create consistent releases and changelogs
license: MIT
compatibility: opencode
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable `gh release create` command

## When to use me
Use this when you are preparing a tagged release.
Ask clarifying questions if the target versioning scheme is unclear.

4.4 自定义 Command

{
  "command": {
    "test": {
      "template": "Run the full test suite with coverage report.\nShow any failures and suggest fixes.",
      "description": "Run tests with coverage",
      "agent": "build"
    },
    "component": {
      "template": "Create a new React component named $ARGUMENTS with TypeScript.",
      "description": "Create a new component"
    }
  }
}

5. 实战速查手册

5.1 CLI 命令速查

命令说明常用参数
opencode启动 TUI-c 继续上次会话, -m model
opencode run "msg"非交互模式执行--auto, --agent, --model
opencode serve启动 HTTP API 服务--port, --hostname
opencode web启动 Web 界面--port
opencode attach <url>连接远程 OpenCode 后端-c, -s sessionID
opencode agent create交互式创建 Agent--path, --description
opencode agent list列出所有 Agent
opencode auth login配置 LLM 提供商-p provider
opencode models列出可用模型--refresh, --verbose
opencode session list列出会话历史-n N
opencode stats查看 Token 用量/费用--days N
opencode export [id]导出会话--sanitize
opencode import <file>导入会话
opencode mcp add添加 MCP 服务器
opencode upgrade [ver]升级版本-m method
opencode debug config调试配置解析
opencode uninstall卸载--keep-config

5.2 内置工具速查

工具功能权限键
bash执行 shell 命令bash
edit精确字符串替换编辑文件edit
write创建/覆写文件edit(共用)
apply_patch应用 patch 文件edit(共用)
read读取文件(支持行范围)read
grep正则搜索文件内容grep
glob通配符匹配文件路径glob
task启动子 Agenttask
skill加载 SKILL.mdskill
todowrite管理 TODO 列表todowrite
webfetch获取网页内容webfetch
websearch网络搜索(Exa)websearch
question向用户提问question
lspLSP 代码智能(实验性)lsp
安全守卫:external_directory(默认 ask)控制访问工作区外目录;doom_loop(默认 ask)检测同一工具调用重复 3 次时触发。

5.3 内置 Agent 速查

Agent类型能力典型用途
BuildPrimary(默认)全部工具启用日常开发、编写代码
PlanPrimary只读分析,edit/bash 需审批代码分析、制定计划、探索未知代码库
GeneralSubagent全工具(无 todo),可写文件复杂搜索、多步任务、并行工作
ExploreSubagent只读,不可修改文件快速搜索文件/关键词、理解代码库
ScoutSubagent只读,用于外部依赖研究克隆依赖仓库、检查库源码、对照上游实现
CompactionPrimary(隐藏)自动压缩长上下文系统自动触发
TitlePrimary(隐藏)生成会话标题系统自动触发
SummaryPrimary(隐藏)生成会话摘要系统自动触发
用 Tab 键在 Primary Agent 之间切换。用 @agent-name 手动调用 Subagent。可通过 opencode.json 中的 default_agent 设置默认 Agent。

5.4 配置键速查

配置键类型说明
modelstring主模型 (provider/model-id)
small_modelstring轻量任务模型(标题生成等)
default_agentstring默认 Agent (build / plan)
subagent_depthint子 Agent 嵌套深度(默认 1)
providerobject提供商配置(timeout, baseURL, models 等)
permissionobject / string工具权限(allow / ask / deny)
agentobject自定义 Agent 定义
commandobject自定义命令模板
toolsobject全局工具开关(废弃,建议用 permission)
instructionsstring[]额外指令文件路径(支持 glob / URL)
pluginstring[]插件 npm 包列表
mcpobjectMCP 服务器配置
lspbool / objectLSP 语言服务器配置
formatterbool / object代码格式化器配置
shellstringShell 类型 (zsh / bash / pwsh)
serverobjectHTTP 服务器设置(port, hostname, cors, mdns)
sharestring分享模式(manual / auto / disabled)
snapshotbool启用文件快照(用于 /undo)
autoupdatebool / "notify"自动更新
compactionobject上下文压缩(auto, prune, reserved)
watcherobject文件监控忽略模式
attachment.imageobject图片附件尺寸限制
disabled_providersstring[]禁用的提供商 ID
enabled_providersstring[]启用的提供商白名单
experimentalobject实验性功能开关

配置加载优先级

  1. Remote config — .well-known/opencode(组织默认)
  2. Global config — ~/.config/opencode/opencode.json(用户偏好)
  3. Custom config — OPENCODE_CONFIG 环境变量
  4. Project config — 项目根 opencode.json
  5. .opencode 目录 — agents、commands、plugins、skills
  6. Inline config — OPENCODE_CONFIG_CONTENT 环境变量
  7. Managed config — 系统管理路径(macOS/Linux/Windows)
  8. macOS MDM — .mobileconfig 托管(最高优先级)
配置采用合并策略(非替换),后加载的覆盖冲突键。托管设置(managed)不可被用户覆盖。

权限模型速查

值行为
"allow"无需审批,自动执行
"ask"弹出审批提示(once / always / reject)
"deny"完全阻止

权限键支持通配符匹配,最后匹配规则生效。典型模式:

{
  "permission": {
    "bash": {
      "*": "ask",
      "git status *": "allow",
      "rm *": "deny"
    }
  }
}

TUI 常用按键

操作快捷键
切换 Agent(Plan ↔ Build)Tab
提交消息Enter
命令面板Ctrl+P(可配置)
撤销变更/undo
重做变更/redo
初始化项目/init
连接提供商/connect
选择模型/models
分享会话/share
@ 文件引用@ + 模糊搜索

6. Agent System Prompt 深度梳理

以下从 ReAct 范式 出发,梳理 OpenCode 如何通过 Prompt 设计实现 Reasoning + Acting 的自主循环。源码路径:packages/opencode/src/session/prompt/、packages/opencode/src/agent/prompt/、packages/core/src/session/runner/。

6.1 ReAct 循环总览

核心本质:OpenCode 的 Agent 能力本质上是 Prompt 驱动的 ReAct 循环 — System Prompt 告诉 LLM"你是谁 + 有什么工具 + 怎么用",然后通过 while(true) 循环 反复执行 推理(Thought) → 行动(Tool Call) → 观察(Tool Result) → 继续推理 → ... 直到任务完成或达到步数上限。
ReAct 循环活动图
图 6:OpenCode ReAct 推理-行动循环(V1 路径,prompt.ts:1081-1338)

V1 循环(packages/opencode/src/session/prompt.ts:1081-1338 runLoop())和 V2 循环(packages/core/src/session/runner/llm.ts:378-401)共享同一核心模式:

  1. LLM 推理 → 产生文本或工具调用(tool_call)
  2. 权限检查 → allow/ask/deny
  3. 执行工具 → 产生输出(tool_result)
  4. 工具结果发送回 LLM → LLM 观察结果,决定下一步
  5. 继续循环 → 直到 LLM 输出纯文本(无工具调用),或到达 maxSteps 上限

6.2 Prompt 中驱动 ReAct 循环的关键文本

不同模型版本的系统提示词中,编码了不同强度的 ReAct 循环指令。以下是各模型版本中明确驱回 ReAct 行为的原文:

模型文件ReAct 强度关键驱动文本(原文摘录)
beast
(gpt-4/o1/o3)
beast.txt 最强 ★★★★★

"You are opencode, an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user."

"You MUST iterate and keep going until the problem is solved."

"Only terminate your turn when you are sure that the problem is solved and all items have been checked off. Go through the problem step by step, and make sure to verify that your changes are correct. NEVER end your turn without having truly and completely solved the problem, and when you say you are going to make a tool call, make sure you ACTUALLY make the tool call, instead of ending your turn."

"You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully."

"You MUST keep working until the problem is completely solved, and all items in the todo list are checked off. Do not end your turn until you have completed all steps..."

"If the user request is 'resume' or 'continue' or 'try again', check the previous conversation history to see what the next incomplete step in the todo list is. Continue from that step, and do not hand back control to the user until the entire todo list is complete..."

copilot-gpt-5 copilot-gpt-5.txt 最强 ★★★★★

"You are an agent - you must keep going until the user's query is completely resolved, before ending your turn and yielding back to the user."

"You MUST iterate and keep going until the problem is solved."

"Do not repeat yourself after tool calls; continue from where you left off."

kimi kimi.txt 较强 ★★★★

"The results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information."

"Do not give up too early."

gemini gemini.txt 较强 ★★★★

"Finally, you are an agent - please keep going until the user's query is completely resolved."

anthropic
(claude)
anthropic.txt 中等 ★★★

"You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress." — 通过 TodoWrite 间接驱动多步执行。

"It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed."

"If the user asks for a 'resume' or 'continue'..." — 支持续跑指令。

default default.txt 隐式 ★★

没有显式的"必须循环/不走好不停"指令。依靠工具使用示例(如 write tests example)隐式展示多步工作流,依赖模型自身的推理能力驱动循环。

trinity trinity.txt 隐式 ★★

"Use exactly one tool per assistant message. After each tool call, wait for the result before continuing." — 强制串行单步,天然的 ReAct 节奏但无循环驱动力。

gpt gpt.txt 隐式 ★★

"Persist until the task is fully handled end-to-end within the current turn whenever feasible: do not stop at analysis or partial fixes; carry changes through implementation, verification, and a clear explanation of outcomes..."

codex codex.txt 隐式 ★★

"Default: do the work without asking questions. Treat short tasks as sufficient direction; infer missing details by reading the codebase and following existing conventions."

关键发现:beast 和 copilot-gpt-5 的 Prompt 通过大量"自主完成"指令实现了最强 ReAct 驱动;而 default/anthropic 版本更依赖模型自身推理能力隐式驱动循环。这说明 OpenCode 不是用一段代码实现 ReAct,而是用 Prompt 文本的措辞强度来控制 ReAct 行为的活跃度。

6.3 消息结构:ReAct 循环的物质基础

ReAct 循环之所以能运行,核心在于 工具调用/结果的往返消息格式。每一次 ReAct 循环步(step)在协议层就是一组消息:

┌─────────────────────────────────────────────┐
│ Step N:   assistant(tool_call: read "foo.ts")│  ← LLM 推理出"需要读文件" → 行动
│            ↓                                 │
│           tool_result("foo.ts" 的内容)        │  ← 工具执行完毕 → 观察
│            ↓                                 │
│ Step N+1: assistant(tool_call: edit "foo.ts")│  ← LLM 根据结果决定下一步 → 再行动
│            ↓                                 │
│           tool_result("edit OK")              │
│            ↓                                 │
│ Step N+2: assistant(text: "修改完成")         │  ← LLM 判断任务完成 → 输出纯文本 → 循环结束
└─────────────────────────────────────────────┘

消息转换源码:

文件功能
packages/opencode/src/session/message-v2.ts:131-415toModelMessagesEffect() — 将内部 ToolPart 状态(pending/running/completed/error)转换为 AI SDK 的 tool_call/tool_result 消息对
packages/core/src/session/runner/to-llm-message.ts:30-113V2 的消息转换:ToolCallPart.make() + ToolResultPart.make()
packages/opencode/src/session/llm/ai-sdk.ts:220-247tool-call / tool-result 事件格式定义
防 dangling tool_use:message-v2.ts:349-360 确保每个 tool_use 都有对应的 tool_result — 如果工具未完成就被中断(pending/running),会生成 error tool_result:"[Tool execution was interrupted]"。这是因为 Anthropic/Claude 协议要求每个 tool_use 必须有配套 tool_result。

6.4 ReAct 循环的工程约束

6.4.1 Max Steps 上限

循环不是无限的。每个 Agent 有 steps 配置(默认 Infinity),当到达上限时注入 MAX_STEPS_PROMPT:

CRITICAL - MAXIMUM STEPS REACHED

The maximum number of steps allowed for this task has been reached. Tools are
disabled until next user input. Respond with text only.

STRICT REQUIREMENTS:
1. Do NOT make any tool calls
2. MUST provide a text response summarizing work done so far
3. This constraint overrides ALL other instructions

Response must include:
- Statement that maximum steps have been reached
- Summary of what has been accomplished
- List of any remaining tasks not completed
- Recommendations for next steps
V1 vs V2 区别:V1(prompt.ts:1280)到达上限时注入该 Prompt 作为 assistant 消息让 LLM 总结;V2(runner/llm.ts:206)除此还设置 toolChoice: "none" 在协议层禁用工具调用。

6.4.2 Doom Loop 检测

packages/opencode/src/session/processor.ts:354-377 — 如果同一 assistant 消息的最后 3 次工具调用(DOOM_LOOP_THRESHOLD = 3)是相同工具+相同参数,触发 doom_loop 权限请求,阻止 LLM 陷入死循环。

6.4.3 续跑提示

过程中多处注入提示促使 LLM 继续循环:

场景位置文本
子 Agent 返回prompt.ts:446"Summarize the task tool output above and continue with your task."
上下文压缩后compaction.ts:481-485"Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
Plan→Build 切换build-switch.txt"Your operational mode has changed from plan to build. You are no longer in read-only mode. You are permitted to make file changes, run shell commands..."

6.5 Prompt 组装流程

Prompt 组装流程
图 7:OpenCode System Prompt 组装流程

组装公式(V1 路径):

// 1. 选择基础 System Prompt(根据 model ID)
system = agent.customPrompt || model-base.txt

// 2. 拼接用户自定义 system(来自 opencode.json config 的 system 字段)
system += user.system

// 3. 拼接环境上下文
system += env_info  // model, workspace, git status, platform, date

// 4. 拼接指令文件
system += AGENTS.md / CLAUDE.md / project instructions

// 5. 拼接工具上下文
system += MCP tools + available skills list

关键源码位置:

文件行功能
packages/opencode/src/session/system.ts26-40provider() — 根据 model ID 选择 .txt 提示词
packages/opencode/src/session/system.ts58-126environment() / skills() / mcp() — 生成环境/技能/MCP 上下文
packages/opencode/src/session/llm/request.ts58-66LLM 请求准备:拼接 agent.prompt + model-base + user.system
packages/opencode/src/session/prompt.ts1081-1338V1 ReAct while(true) 循环(核心!)— runLoop()
packages/core/src/session/runner/llm.ts378-401V2 ReAct 循环 — while(needsContinuation)
packages/opencode/src/session/instruction.ts155-169Instruction.system() — 加载 AGENTS.md / CLAUDE.md
packages/opencode/src/session/reminders.ts15-90SessionReminders.apply() — 注入 plan/build mode 提示
packages/core/src/session/runner/max-steps.ts1-16MAX_STEPS_PROMPT — 步数上限提示
packages/opencode/src/session/processor.ts354-377DOOM_LOOP_THRESHOLD — 死循环检测

6.6 模型专属 System Prompt 汇总

位于 packages/opencode/src/session/prompt/*.txt,由 SystemPrompt.provider(model) 选择。

Match文件行数身份定义(第一句话)
默认default.txt95"You are opencode, an interactive CLI tool that helps users with software engineering tasks."
claudeanthropic.txt105"You are OpenCode, the best coding agent on the planet."
gpt-4/o1/o3beast.txt147"You are opencode, an agent - please keep going until the user's query is completely resolved..."
gemini-gemini.txt155"You are opencode, an interactive CLI agent specializing in software engineering tasks."
gpt(其他)gpt.txt107"You are OpenCode, You and the user share the same workspace and collaborate to achieve the user's goals."
gpt+codexcodex.txt79"You are OpenCode, the best coding agent on the planet."
trinitytrinity.txt97"You are opencode, an interactive CLI tool..."
kimikimi.txt95"You are OpenCode, an interactive general AI agent running on a user's computer."
copilot-gpt-5copilot-gpt-5.txt143"You are an expert AI programming assistant. Your name is opencode." — 含 GPT-specific 工具(semantic_search, run_in_terminal)
模型选择逻辑(packages/opencode/src/session/system.ts:26-40):按优先级 claude > gpt-4/o1/o3 > gemini- > gpt/codex > trinity > kimi > default 依次匹配 model ID。

6.7 子 Agent 专用 Prompt

子 Agent 启动时用自己的 System Prompt 替换主 Agent 的模型专属 Prompt。位于 packages/opencode/src/agent/prompt/*.txt,packages/core/src/plugin/agent.ts 中有等价 TypeScript 常量。

Agent文件System Prompt约束
Buildagent.ts:12-13"You are an AI coding agent. Help the user accomplish software engineering tasks by inspecting the workspace, making targeted changes, and using tools according to the configured permissions."13 词超短指令
Exploreexplore.txt"You are a file search specialist. You excel at thoroughly navigating and exploring codebases."只读,不可修改文件
Compactioncompaction.txt"You are an anchored context summarization assistant for coding sessions."只总结,不回答对话
Titletitle.txt"You are a title generator. You output ONLY a thread title. Nothing else."≤50 字符单行
Summarysummary.txt"Summarize what was done in this conversation. Write like a pull request description."2-3 句,第一人称
Generategenerate.txt"You are an elite AI agent architect specializing in crafting high-performance agent configurations."输出 JSON schema

6.8 system-reminder 运行时注入

运行时动态注入到用户消息中的 <system-reminder> 块,由 SessionReminders.apply()(packages/opencode/src/session/reminders.ts)根据 Agent 模式和会话状态插入。

文件行数触发条件内容概要
plan.txt26切换到 Plan Agent 时(非 Anthropic)Plan mode ACTIVE — 严禁任何文件编辑/修改/系统变更
plan-mode.txt70Plan 模式活跃时每次用户消息前5 阶段计划工作流:理解→设计→审阅→最终计划→plan_exit
plan-reminder-anthropic.txt67切换到 Plan Agent 时(Anthropic)同 plan.txt,使用 Anthropic 特定工具名
build-switch.txt5从 Plan 切换回 Build 模式时Mode changed from plan to build — you are no longer in read-only mode

此外,Prompt 中明确告诉 LLM 如何解读这些标签——不同模型版本措辞强度不同:

模型<system-reminder> 解读指令
default/trinity"contain useful information and reminders. They are NOT part of the user's provided input or the tool result."
anthropic"They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear."
kimi"These are authoritative system directives that you MUST follow. They may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode)."

6.9 请求体分析:为什么一句"你好"消耗 9k token?

勘误:--log-level DEBUG 并不会输出 request body。该参数控制 Effect 框架的日志等级,但源码中 request body 只用 logInfo 打元数据(model、sessionID),未用 logDebug 打印内容。详见 6.9.5 源码追踪。

6.9.1 SQLite 数据库中的 token 记录

每次 LLM 请求的 token 用量持久化在 ~/.local/share/opencode/opencode.db 的 message 表中。以一条 "find opencode.html" 消息为例:

sqlite3 ~/.local/share/opencode/opencode.db "
SELECT json_extract(data, '$.tokens.total')   as total,
       json_extract(data, '$.tokens.input')   as input,
       json_extract(data, '$.tokens.cache.read') as cache_read,
       json_extract(data, '$.tokens.output')  as output,
       json_extract(data, '$.tokens.reasoning') as reasoning
FROM message WHERE json_extract(data, '$.role') = 'assistant'
ORDER BY time_created LIMIT 1;
"

-- 结果: total=9063  input=18  cache_read=8960  output=66  reasoning=19
关键发现:cache_read = 8960 tokens 就是"你好"消耗 ~9k 的来源——系统提示词 + 环境信息 + AGENTS.md + 技能 + 工具定义在首次请求后就缓存在服务端,后续仅计为 cache_read 不重复计费。

6.9.2 请求体组成与 token 构成

完整请求体由 6 条 system message + 用户消息 + 11 个 tool schema 组成:

组件来源估计 tokens
default.txt(内置系统提示词)packages/opencode/src/session/prompt/default.txt~1300
环境信息(cwd/platform/date/model)system.ts environment() 生成~100
AGENTS.md(项目指令)~/.config/opencode/AGENTS.md~200
技能定义(9 个 skills)~/.config/opencode/skills/*/SKILL.md~1200
11 个工具 JSON Schemabash/read/write/edit/glob/grep/task/webfetch/skill/question/todowrite~3100
风格指令 + 示例 + policy 等default.txt 后半部分~3060
cache_read 合计~8960
用户消息 "你好"input tokens~5
首轮 total~9000
图 6.9: cache_read 9600 tokens 构成(饼图)
  cache_read 9600 tokens 构成:

     default.txt       ████████████████░░░░░  1300  (14.5%)
     风格指令+示例        ██████████████████████████████████████░░  3060  (34.1%)
     工具 JSON Schema    ███████████████████████████████████████  3100  (34.6%)
     技能定义            ██████████████░░  1200  (13.4%)
     AGENTS.md          ██░  200  (2.2%)
     环境信息            █░  100  (1.1%)

6.9.3 请求组装流程(源码级)

每次 LLM 调用的请求体在 llm/request.ts 的 LLMRequestPrep.prepare() 中内存组装,组装完直接传给 streamText(),中间无任何日志或持久化:

// prompt.ts:1257-1286 — 每个 loop 迭代组装 system + messages + tools
const [skills, env, instructions, mcpInstructions, modelMsgs] =
  yield* Effect.all([
    sys.skills(agent),                         // 技能描述
    sys.environment(model),                    // 环境信息 + AGENTS.md 内容
    instruction.system().pipe(Effect.orDie),   // AGENTS.md 路径
    sys.mcp(agent, session.permission),        // MCP 指令(如有)
    MessageV2.toModelMessagesEffect(msgs, model), // 对话历史
  ])

const system = [...env, ...instructions, ...(mcpInstructions ? [mcpInstructions] : []), ...(skills ? [skills] : [])]

// llm/request.ts:104 — 拼接 final messages
const messages = [
  ...system.map((x) => ({ role: "system", content: x })),
  ...input.messages,
]

// 然后直接 streamText({ messages, tools, ... }) → HTTP POST 到 API

6.9.4 真正能看到请求体的方法

方法说明推荐度
抓包工具 macOS: Proxyman / Charles;通用: mitmproxy。设置 HTTPS_PROXY=http://localhost:8080 后启动 opencode,可捕获完整 HTTP request/response body 推荐
改源码 在 llm/request.ts 的 prepare() 函数 return 前加 console.log(JSON.stringify({ messages, tools })),重新编译运行 适合开发者
数据库查询 opencode.db 的 message 表记录每轮的 token 用量(total/input/cache/output),part 表记录完整对话内容,但 system prompt 不落库 做 token 分析
还原估算 根据源码 + 本地配置文件手动拼装,参见 6.9.2 的 token 分解表 理解结构

6.9.5 源码级追踪

对于 DeepSeek 等非 GPT/Claude/Gemini 模型,使用的默认 system prompt:

文件内容
packages/opencode/src/session/prompt/default.txt默认 System Prompt(适用 DeepSeek、Mistral 等)
packages/opencode/src/session/system.tsprovider(model) 根据模型类型选 prompt 文件;environment() 生成环境信息;skills() 格式化技能列表
packages/opencode/src/session/instruction.ts读取 AGENTS.md / CLAUDE.md,system() 返回全局 + 项目指令文本
packages/opencode/src/session/llm/request.tsLLMRequestPrep.prepare():拼接 system[] + messages[] → 完整请求体
packages/opencode/src/session/prompt.tsloop() 中的主循环:每次迭代调用上述流程,将 system + messages + tools 传给 LLM

6.9.6 还原演示:完整请求体 JSON

根据源码 + AGENTS.md + 技能列表 + 工具 schema 还原的"你好"请求体(下载 JSON):

点击展开完整 JSON(6 条 system message + 1 条 user message + 11 个 tool schema)
{
  "model": "deepseek/deepseek-v4-pro",
  "temperature": 0.3,
  "messages": [
    {
      "role": "system",
      "content": "You are opencode, an interactive CLI tool that helps users with software engineering tasks..."
    },
    {
      "role": "system",
      "content": "You are powered by the model named deepseek-v4-pro...\n<env>\n  Working directory: /Users/brian/microarch\n  Platform: darwin\n</env>\nInstructions from: /Users/brian/.config/opencode/AGENTS.md\n# AGENTS\n## Microarch 公司结构\n..."
    },
    {
      "role": "system",
      "content": "Skills provide specialized instructions...\n<available_skills>\n  <skill><name>find-file</name>...</skill>\n  <skill><name>fix-uml</name>...</skill>\n  <skill><name>local-ocr</name>...</skill>\n  <skill><name>opencloudos-restart-bot</name>...</skill>\n  <skill><name>pull-all</name>...</skill>\n  <skill><name>push-all</name>...</skill>\n  <skill><name>send-email</name>...</skill>\n  <skill><name>teardown</name>...</skill>\n  <skill><name>ubuntu-restart-bot</name>...</skill>\n  <skill><name>customize-opencode</name>...</skill>\n</available_skills>"
    },
    {
      "role": "system",
      "content": "## Tools\n\nYou have access to a set of tools to help answer the user's questions..."
    },
    {
      "role": "user",
      "content": "你好"
    }
  ],
  "tools": [
    { "type": "function", "function": { "name": "bash",       "description": "Executes a given bash command...",              "parameters": { "type":"object", "properties":{ "command":{},"workdir":{},"timeout":{} }, "required":["command"] } } },
    { "type": "function", "function": { "name": "edit",       "description": "Performs exact string replacements...",          "parameters": { "type":"object", "properties":{ "filePath":{},"oldString":{},"newString":{},"replaceAll":{} }, "required":["filePath","oldString","newString"] } } },
    { "type": "function", "function": { "name": "glob",       "description": "Fast file pattern matching tool...",              "parameters": { "type":"object", "properties":{ "pattern":{},"path":{} }, "required":["pattern"] } } },
    { "type": "function", "function": { "name": "grep",       "description": "Fast content search tool...",                    "parameters": { "type":"object", "properties":{ "pattern":{},"path":{},"include":{} }, "required":["pattern"] } } },
    { "type": "function", "function": { "name": "question",   "description": "Use this tool when you need to ask the user...", "parameters": { "type":"object", "properties":{ "questions":{} }, "required":["questions"] } } },
    { "type": "function", "function": { "name": "read",       "description": "Read a file or directory...",                    "parameters": { "type":"object", "properties":{ "filePath":{},"offset":{},"limit":{} }, "required":["filePath"] } } },
    { "type": "function", "function": { "name": "skill",      "description": "Load a specialized skill...",                    "parameters": { "type":"object", "properties":{ "name":{} }, "required":["name"] } } },
    { "type": "function", "function": { "name": "task",       "description": "Launch a new agent...",                           "parameters": { "type":"object", "properties":{ "description":{},"prompt":{},"subagent_type":{} }, "required":["description","prompt","subagent_type"] } } },
    { "type": "function", "function": { "name": "todowrite",  "description": "Create and maintain a structured task list...",  "parameters": { "type":"object", "properties":{ "todos":{} }, "required":["todos"] } } },
    { "type": "function", "function": { "name": "webfetch",   "description": "Fetches content from a specified URL...",        "parameters": { "type":"object", "properties":{ "url":{} }, "required":["url"] } } },
    { "type": "function", "function": { "name": "write",      "description": "Writes a file to the local filesystem...",       "parameters": { "type":"object", "properties":{ "content":{},"filePath":{} }, "required":["content","filePath"] } } }
  ],
  "total_estimated_tokens": 9063,
  "token_breakdown": {
    "cache_read": 8960,
    "input": 18,
    "output": 66,
    "reasoning": 19
  },
  "cache_read_breakdown": {
    "default_txt_system_prompt": "~1300 tokens",
    "AGENTS_md": "~200 tokens",
    "environment_info": "~100 tokens",
    "skills_definitions": "~1200 tokens",
    "tool_definitions_schemas": "~3100 tokens",
    "other_system_instructions": "~3060 tokens"
  }
}

注:工具 schema 中每个 parameters.properties 仅保留了 key,省略了 type/description 以节省篇幅。真实请求中的 tool schema 更冗长(含完整的 JSON Schema description),占 cache_read 的大头(~34.6%)。